/*
 * Created on 28 avr. 2005
 * Group of SIMPACK 2004-2005
 * Coded by TRAN Thuy Tien
 */
package communication.thirdLayer;

import communication.pack.*;
import objects.*;
import java.util.*;
import communication.*;

/**
 * @author Tien
 */
public class RouterAODV extends Router {
	
/**
 * Default constructor
 */
	public RouterAODV() {
		super();
	}
	
/**
 * Constructor from a station s where this router belongs
 * @param s
 */
	public RouterAODV(Station s) {
		super(s);
	}

	/**
	 * Take into account the route leading to the primary sender of the package
	 * which is one of the parameters
	 * 
	 * @param p
	 *            package the type RouteRequest or RouteReply
	 * @param transmitter:
	 *            direct transmitter of package p
	 * @return true if the routing table changes
	 */

	public int routingTableUpdate(SMPackage p, Station transmitter) {
		
		this.razEntry(p.getSenderId());
		this.razEntry(transmitter);
		if ((p instanceof RouteRequest)
				|| (p instanceof RouteReply || (p instanceof HelloAODV))) {
			// premier entry: route vers la source de ce paquet
			RoutingTableEntry entry1 = new RoutingTableEntry(p.getSenderId(),
					transmitter, p.getSequenceNo(), p.getHopCount());
			if(entry1.getDestination()!=this.getStation())
				this.addRoutingTableEntry(entry1);

			if (p instanceof RouteReply) {
				// deuxieme entry: route vers la destination demandee
				RoutingTableEntry entry2 = new RoutingTableEntry(
						((RouteReply) p).getRequestDestinationId(),
						transmitter, ((RouteReply) p).getRequestDestSeqNo(),
						((RouteReply) p).getRequestDestHopCount()
								+ p.getHopCount());

				// this.addRoutingTableEntry(entry1);
				if(entry1.getDestination()!=this.getStation())
					this.addRoutingTableEntry(entry2);
				return 2;
			}
			return 1;
		} else
			return 0;
	}

	/**
	 * Increment the timer of all the routing table entries
	 */
	public void timerIncrement() {
			for (int i = 0; i < this.getRoutingTable().size(); i++) {
			RoutingTableEntry entry = (RoutingTableEntry) this
					.getRoutingTable().get(i);
			entry.timerIncrement(1);
		}
	}

	/**
	 * Treatment of a package when the station receives it
	 * 
	 * @param p the package
	 * @param transmitter the station transmitter
	 */
	public void packageTreatment(SMPackage p, Station transmitter) {

		p.decrementTTL();
		p.incrementHopCount();
		if (p.getTTL() >= 0) {
			// 1. Cas de RouteRequest
			if (p instanceof RouteRequest) {

				if (!this.isDuplicated((RouteRequest) p)) {
					Station dest = ((RouteRequest) p).getRequestedDestination();
					this.addInHistoryTable(p);
					this.routingTableUpdate(p, transmitter);
					if (this.getStation().equals(dest)) {
						RouteReply reply = new RouteReply(this.getStation(), p
								.getSenderId(), this.getStation(), this
								.getNoSequence(), 0, p.getRequestId(), this
								.getNoSequence(), 0, this.getTTL());
						this.send(reply);
					} else if (!this.isPresent(dest)) {
						// --------- p.incrementHopCount();
						this.sendToAllExceptOne(p, transmitter);
					} else {
						RoutingTableEntry entry = this.search(dest);
						if (entry.getSequence() > ((RouteRequest) p)
								.getDestinationSequenceNo()) {
							RouteReply reply = new RouteReply(
									this.getStation(), p.getSenderId(), dest,
									entry.getSequence(), 0, p.getRequestId(),
									this.getNoSequence(), entry.getStepCount(),
									this.getTTL());
							this.send(reply);
						} else {
							// ---------- p.incrementHopCount();
							this.sendToAllExceptOne(p, transmitter);
						}
					}

				}
			}// fin de cas RouteRequest

			// 2. Cas de RouteReply
			else if (p instanceof RouteReply) {
				int b = this.routingTableUpdate(p, transmitter);
				System.out.println("je suis dans packageTreatment-> cas RouteReply"
								+ "routingTableUpdate is " + b);
				if (this.getStation().equals(
						((RouteReply) p).getDestinationId())) {
					System.out.println("DataToBeSent size is "
							+ this.getDataToBeSent().size());
					Vector rem = new Vector();
					for (int i = 0; i < this.getDataToBeSent().size(); i++) {
						DataPackage data = (DataPackage) this.getDataToBeSent()
								.elementAt(i);
						if (data.getDestinationId().equals(
								((RouteReply) p).getRequestDestinationId())) {
							System.out.println("yes! i=" + i);
							this.send(data);
							// Modif Tien
							rem.add(data);
						}
					}
					System.out.println("rem size is " + rem.size());
					for (int i = 0; i < rem.size(); i++) {
						this.removeDataPackage((DataPackage) rem.get(i));
					}
				} else {
					this.sendToNext(p);
				}
			}// fin de cas RouteReply

			// 3. Cas de DataPackage
			else if (p instanceof DataPackage) {
				Station sender=p.getSenderId();	
				if (this.getStation().equals(
						((DataPackage) p).getDestinationId())) {
					int file = ((DataPackage) p).getFileId();

					int t=0;
					if(this.getReceivedData().containsKey(p.getSenderId())){
						HashMap h=(HashMap)this.getReceivedData().get(p.getSenderId());	
						if (h.containsKey(new Integer(file)))
							t = ((Integer) h.get(
								new Integer(file))).intValue();
					}

					if (((DataPackage) p).getLength()>t + ((DataPackage) p).getSize()
							-SMPackage.DATAHEADINGSIZE)
						this.addFile(sender,file, t + ((DataPackage) p).getSize()
								-SMPackage.DATAHEADINGSIZE);
					else{
						this.addCompletedFile(sender,file, t
								+ ((DataPackage) p).getSize()
								-SMPackage.DATAHEADINGSIZE);
						this.removeFile(file);
					}
				} else {
					this.sendToNext(p);
				}
			}// Fin de cas DataPackage

			// 4. Cas de Rerr
			else if (p instanceof Rerr) {
				this.purgeFromError((Rerr) p);
				if (((Rerr) p).getDestinationId().equals(this.getStation())) {
					// TODO: signaler par l'IHM que la communication a echouee
				} else {
					this.sendToNext(p);
				}
			}// fin de cas Rerr

			// 5. Cas de HelloAODV
			else {
				this.routingTableUpdate(p, transmitter);
			}// Fin de cas HelloAODV
		} else
			System.out.println("Time to leave\n");

	}

	/**
	 * Purge the invalide routing table entry from the routing table in the
	 * case: the routing table entry that we have possess a sequence number <=
	 * the sequence number of the unreachable destination in the package Rerr
	 * 
	 * @param s
	 *            the invalide destination
	 */
	private void purgeFromError(Rerr r) {
		Station un = ((Rerr) r).getUnreachable();
		int seq = ((Rerr) r).getUnreachableSeqNo();

		RoutingTableEntry e = this.search(un);
		if (e != null) {
			if (e.getSequence() <= seq)
				this.removeRoutingTableEntry(e);
		}

	}

	/**
	 * send a package p
	 * 
	 * @param p the package
	 */
	public void send(SMPackage p) {
		this.incrementRequesCounter();

		if ((p instanceof RouteRequest) || (p instanceof HelloAODV)) {
			if (p instanceof RouteRequest)
				this.incrementSequenceCounter();
			this.sendToAllNeighbors(p);
		}

		else if (p instanceof DataPackage) {
			DataPackage pd = (DataPackage) p;
			Station destination = ((DataPackage) p).getDestinationId();
			Station next = this.routingTableSearch(destination);
			if ((next != null)
			// && (this.isValide(destination))
			)
				this.sendToOneDestination(p, next);
			else if (p.getSenderId().getRouter().equals(this)) {
				// Pour envoyer 1 seul RouteRequest pour 1 fichier envoye (a la
				// place d'envoyer
				// 1 RouteRequest pour chaque paquet de ce fichier)
				boolean existe = false;
				for (int i = 0; i < this.getDataToBeSent().size(); i++) {
					DataPackage temp = (DataPackage) this.getDataToBeSent()
							.get(i);
					if (temp.getFileId() == pd.getFileId())
						existe = true;
				}
				if (!existe)
					this.sendRouteRequest(destination);
				this.addDataPackage((DataPackage) p);
			} else
				this.sendRerr(p);
		}

		else if (p instanceof RouteReply) {
			this.incrementSequenceCounter();
			Station destination = ((RouteReply) p).getDestinationId();
			Station next = this.routingTableSearch(destination);
			if ((next != null)
			)
				this.sendToOneDestination(p, next);
			else
				this.sendRerr(p);
		}

		else if (p instanceof Rerr) {
			Station destination = ((Rerr) p).getDestinationId();
			Station next = this.routingTableSearch(destination);
			if ((next != null)
			)
				this.sendToOneDestination(p, next);
			// si erreur d'un paquet Rerr (destination pas trouvee dans la table
			// de routage)
			// on ne fait rien
		}
	}

	/**
	 * We send a Error Package in case that we don't find the next node to
	 * tranfer this package to in the routing table
	 * 
	 * @param p
	 */
	public void sendRerr(SMPackage p) {
		// envoyer un paquet d'erreur

		int seq;
		if ((p instanceof RouteReply) || (p instanceof DataPackage)
				|| (p instanceof Rerr)) {
			Station dest;
			if (p instanceof RouteReply)
				dest = ((RouteReply) p).getDestinationId();
			else if (p instanceof DataPackage)
				dest = ((DataPackage) p).getDestinationId();
			else
				dest = ((Rerr) p).getDestinationId();
			if (this.isPresent(dest)) {
				RoutingTableEntry e = this.search(dest);
				seq = e.getSequence();
			} else
				seq = -1;
			Rerr error = new Rerr(this.getStation(), this.getNoSequence(), 0,
					this.getNoRequest(), p.getSenderId(), this.getTTL(), dest,
					seq);
			this.send(error);
		}
	}
/**
 * send to RouteRequest package to ask for the road to the seekedDestination
 * @param seekedDestination 
 */
	public void sendRouteRequest(Station seekedDestination) {
		RouteRequest req = new RouteRequest(this.getStation(),
				seekedDestination, this.getNoSequence(), 0,
				this.getNoRequest(), -1, this.getTTL());
		if (this.search(seekedDestination) != null)
			req.setDestinationSequenceNo(seekedDestination.getRouter()
					.getNoSequence());
		this.send(req);
	}

	/**
	 * Transfer the package to the next station. This method is reserved for
	 * one-destination sending.
	 * 
	 * @param p the transfered package
	 */

	public void sendToNext(SMPackage p) {
		if ((p instanceof RouteReply) || (p instanceof DataPackage)
				|| (p instanceof Rerr)) {
			Station dest;
			if (p instanceof RouteReply)
				dest = ((RouteReply) p).getDestinationId();
			else if (p instanceof DataPackage)
				dest = ((DataPackage) p).getDestinationId();
			else
				dest = ((Rerr) p).getDestinationId();
			Station next = this.routingTableSearch(dest);
			if (next != null)
				this.sendToOneDestination(p, next);
			else
				this.sendRerr(p);
		}
	}

	/**
	 * Send the package to one neighbor
	 * 
	 * @param p
	 * @param next:
	 *            the neighbor to transfer the package to
	 */
	public void sendToOneDestination(SMPackage p, Station next) {
		Vector param = new Vector();
		param.addElement((String) new Integer(p.getSize()).toString());

		Vector r = new Vector();
		r.addElement(next);
		WishedEmission we = new WishedEmission(this.getStation(), r, this
				.getStation().getSimulator().getTime(), param, this
				.getStation().getFrameId());
		we.insertWishedEmission();
		
		this.getStation().getSimulator().getCommunicationManager()
				.getTLManager().getCurrentPackages().addSendingPackage(
						this.getStation(),
						new IdentifiedCurrentPackage(next, p,
								IdentifiedCurrentPackage.RECEIVER, we
										.getIdent()));
		
		this.getStation().incrementFrameId();

	}

	/**
	 * Send the package p to all the neighbors
	 * 
	 * @param p
	 */
	public void sendToAllNeighbors(SMPackage p) {
		Vector neighbors = this.getStation().getCurrentReachNeighbors();
		if (neighbors.size() > 0) {

			Vector param = new Vector();
			param.addElement((String) new Integer(p.getSize()).toString());

			for (int i = 0; i < neighbors.size(); i++) {
				Vector dest = new Vector();

				Station station = (Station) neighbors.elementAt(i);
				dest.add(station);

				WishedEmission we = new WishedEmission(this.getStation(), dest,
						this.getStation().getSimulator().getTime(), param, this
								.getStation().getFrameId());

				this.getStation().incrementFrameId();

				boolean b = we.insertWishedEmission();
				// ajouter les paquets dans la liste de sendingPackages (class
				// CurrentPackages)

				this.getStation().getSimulator().getCommunicationManager()
						.getTLManager().getCurrentPackages().addSendingPackage(
								this.getStation(),
								new IdentifiedCurrentPackage(station, p,
										IdentifiedCurrentPackage.RECEIVER, we
												.getIdent()));

			}// fin for

		}
	}

	/**
	 * When transfering a package to everyone else, we have to avoid resend it
	 * to the station from which this station received this package
	 * 
	 * @param p
	 *            the package
	 * @param s
	 *            the station that needs to be excluded
	 */
	public void sendToAllExceptOne(SMPackage p, Station s) {

		// envoyer a tout les voisins de cette station
		Vector neighbors = this.getStation().getCurrentReachNeighbors();
		neighbors.remove(s);
		if (neighbors.size() > 0) {

			Vector param = new Vector();
			param.addElement((String) new Integer(p.getSize()).toString());
			// probleme de l'algo getWay (CSMA/CA) -> on
			// peut envoyer a 1 seul receiver a la fois
			// envoyer a tout les voisins de cette station

			for (int i = 0; i < neighbors.size(); i++) {
				Vector dest = new Vector();

				Station station = (Station) neighbors.elementAt(i);
				dest.add(station);

				WishedEmission we = new WishedEmission(this.getStation(), dest,
						this.getStation().getSimulator().getTime(), param, this
								.getStation().getFrameId());

				this.getStation().incrementFrameId();
					
				boolean b = we.insertWishedEmission();

				// ajouter les paquets dans la liste de sendingPackages (class
				// CurrentPackages)

				this.getStation().getSimulator().getCommunicationManager()
						.getTLManager().getCurrentPackages().addSendingPackage(
								this.getStation(),
								new IdentifiedCurrentPackage(station, p,
										IdentifiedCurrentPackage.RECEIVER, we
												.getIdent()));

			}// fin for
		}
	}

	/**
	 * Remove the invalid RoutingTableEntry that leads to this destination. If
	 * this destination is in the neighborhood, remove also all the
	 * RoutingTableEntries that uses it. This method is called when a
	 * communication with dest failed
	 * 
	 * @param dest
	 *            The failed destination
	 */
	public void failedDestination(Station dest) {

		RoutingTableEntry entry = this.search(dest);
		this.exludeNeighbor(dest);

	}
/**
 * Create a HelloAODV package
 * @return the package created
 */
	public HelloAODV createHelloAODV() {
		HelloAODV hel = new HelloAODV(this.getStation(), this.getNoSequence(),
				0, this.getNoRequest(), 1);
		return hel;
	}

}
